
package petrivis.view;

import java.awt.event.MouseEvent;
import java.util.Iterator;
import javax.swing.SwingUtilities;
import petrivis.model.PetriNet;
import prefuse.controls.ControlAdapter;
import prefuse.visual.DecoratorItem;
import prefuse.visual.EdgeItem;
import prefuse.visual.NodeItem;
import prefuse.visual.VisualItem;

/**
 * Class that implements the Control to fire Transitions doble-clicking on it
 * @author Jorge Munoz
 */
public class FireTransitionControl extends ControlAdapter{

    private String action;

    /**
     * Empty contructor
     */
    public FireTransitionControl(){
        super();
    }

    /**
     * Contrsuctor that indicates the action to be run after the transition
     * is fired
     * @param action Name of the action to be runned after the fire of the
     * transition
     */
    public FireTransitionControl(String action){
        super();
        this.action = action;
    }

    /**
     * If double-ckicking is done over a transition that can be fired,
     * the transitions will be fired
     * @param item Item over the one the double-clicking has been done.
     * @param e MouseEvent over the PetriNet element
     */
    @Override
    public void itemClicked(VisualItem item, MouseEvent e) {
        //If Double click with left button
        if (SwingUtilities.isLeftMouseButton(e) && e.getClickCount() == 2) {

            //If it is a Decorator
            if (item instanceof DecoratorItem){

                //Check with its decorated item
                itemClicked(((DecoratorItem)item).getDecoratedItem(),e);
            }
            //If it is a Transition
            else if (item instanceof NodeItem &&
                !((Boolean)item.get(PetriNet.PLACE_COL)).booleanValue()){

                //If it can be fired, fire it
                if(canBeFired((NodeItem)item)){
                    fire((NodeItem)item);
                    //Run action after fire the transition
                    if(action != null) item.getVisualization().run(action);
                }
            }
        }
    }

    /**
     * Check if the transition can be fired
     * @param item Node item of the transition to be fired
     * @return true if the transition can be fire, otherwide false
     */
    private boolean canBeFired(NodeItem item){
        boolean canFire = true;

        Iterator iterInEdges = item.inEdges();
        //If there are more input edges to check and
        //fire the transitions it's even possible
        while (iterInEdges.hasNext() && canFire) {

            //Get the input edge and the input Node
            EdgeItem edge = (EdgeItem) iterInEdges.next();
            NodeItem sourceNode = edge.getSourceItem();

            //Get the weight of the Arc
            Integer weight = (Integer) edge.get(PetriNet.WEIGHT_COL);
            if(weight == null) weight = new Integer(1);

            //Get the tokens of the Input Place
            Integer marking = (Integer) sourceNode.get(PetriNet.MAR_COL);
            if(marking == null) marking = new Integer(0);

            //Check if the number of tokens is enough
            if(marking < weight) canFire = false;
	    }

        return canFire;
    }

    /**
     * Fire a Transition
     * @param item Node item of the transition to be fired
     */
    private void fire(NodeItem item) {

        //Remove the input tokens
        Iterator iterInEdges = item.inEdges();
        while (iterInEdges.hasNext()) {

            //Get the input edge and the input Node
            EdgeItem edge = (EdgeItem) iterInEdges.next();
            NodeItem sourceNode = edge.getSourceItem();

            //Get the weight of the Arc
            Integer weight = (Integer) edge.get(PetriNet.WEIGHT_COL);
            if(weight == null) weight = new Integer(1);

            //Get the tokens of the Input Place
            Integer marking = (Integer) sourceNode.get(PetriNet.MAR_COL);
            if(marking == null) marking = new Integer(0);

            //Set the new marking
            sourceNode.set(PetriNet.MAR_COL, marking-weight);
        }

        //Add the output tokens
        Iterator iterOutEdges = item.outEdges();
        while(iterOutEdges.hasNext()) {

            //Get the output edge and the output Node
            EdgeItem edge = (EdgeItem) iterOutEdges.next();
            NodeItem targetNode = edge.getTargetItem();

            //Get the weight of the Arc
            Integer weight = (Integer) edge.get(PetriNet.WEIGHT_COL);
            if(weight == null) weight = new Integer(1);

            //Get the tokens of the Output Place
            Integer marking = (Integer) targetNode.get(PetriNet.MAR_COL);
            if(marking == null) marking = new Integer(0);

            //Set the new marking
            targetNode.set(PetriNet.MAR_COL, marking+weight);
        }
    }
}
